package taskhandler

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"sync"

	"cvevulner/common"
	"cvevulner/models"
	"cvevulner/util"

	"github.com/astaxie/beego"
	"github.com/astaxie/beego/logs"
)

var lock sync.Mutex

func GetYamlTables(url string) (string, error) {
	compURL := url + "/lifeCycle/tables"
	body, err := util.HTTPGetCom(compURL)
	if err == nil && body != nil {
		var respBody map[string]interface{}
		err = json.Unmarshal(body, &respBody)
		if err != nil {
			logs.Error(err)
			return "", err
		}
		logs.Info(respBody)
		if respBody["code"].(string) == "2001" {
			for _, values := range respBody["data"].([]interface{}) {
				var gt models.GitPackageTable
				gt.TableName = values.(string)
				tableID, err := models.CreateYamlTable(&gt)
				if tableID > 0 {
					logs.Info("table: ", values, "Inserted successfully, table_id: ", tableID)
				} else {
					logs.Error("table: ", values, "Insertion failed, err: ", err)
					return "", err
				}
			}
		} else {
			return "", errors.New("数据格式错误")
		}
	}
	return "", nil
}

//GetYamlByGit
func GetYamlByGit(url string) (string, error) {
	defer common.Catchs()
	var gt []models.GitPackageTable
	page := 1
	size := 20
	num, err := models.GetYamlTable(&gt)
	if err != nil {
		logs.Error("Query table failed, unable to get yaml, err: ", err)
		return "", err
	}
	logs.Info("There are: ", num, ", Table data needs to be obtained, ", gt)
	compURL1 := url + "/packages"
	var ch = make(chan int, len(gt))
	for i, tableValue := range gt {
		go func(tv models.GitPackageTable, idx, locPage, locSize int, locUrl, locCompURL1 string) {
			ok, err := GetYaml(locUrl, locCompURL1, locPage, locSize, tv, &ch)
			if err == nil {
				logs.Info("The current data processing is successful,i: ", idx)
			} else {
				logs.Error("The current data processing failed, ok: ", ok, ",i: ", idx, ", err: ", err)
			}
		}(tableValue, i, page, size, url, compURL1)
	}
	for i := 0; i < len(gt); i++ {
		<-ch
	}
	close(ch)
	return "", nil
}

func GetYaml(url, compURL1 string, page, size int,
	tableValue models.GitPackageTable, ch *chan int) (string, error) {
	defer common.Catchs()
	var tc GitTablePackCount
	tc.TableName = tableValue.TableName
	tc.Page = 0
	tc.Size = 0
	tc.Page = page
	tc.Size = size
	compURL2 := compURL1 + "?table_name=" + tableValue.TableName
	for {
		compURL := compURL2 +
			"&page_num=" + strconv.Itoa(tc.Page) + "&page_size=" + strconv.Itoa(size)
		body, err := util.HTTPGetCom(compURL)
		var respBody map[string]interface{}
		if err == nil && body != nil {
			err = json.Unmarshal(body, &respBody)
			if err != nil {
				logs.Error(err)
				return "", err
			}
		} else {
			logs.Error("http request failed, url: ", compURL)
			return "", err
		}
		logs.Info("start: ", tc)
		if respBody["code"].(string) == "2001" {
			curCount := 0
			if respBody == nil || respBody["data"] == nil || len(respBody["data"].([]interface{})) == 0 {
				logs.Error("Data is empty, url: ", compURL)
				return "", err
			}
			for i, values := range respBody["data"].([]interface{}) {
				tc.Count = tc.Count + 1
				if values == nil || values == "" || len(values.(map[string]interface{})) == 0 {
					curCount = curCount + 1
					continue
				}
				valuesX := values.(map[string]interface{})
				lock.Lock()
				ok, err := ProcPackDetail(url, valuesX, tableValue, i)
				lock.Unlock()
				logs.Info("ok: ", ok, ", err: ", err)
				curCount = curCount + 1
			}
			totalPage := 0
			switch respBody["total_page"].(type) {
			case string:
				totalPage, _ = strconv.Atoi(respBody["total_page"].(string))
			case int:
				totalPage = respBody["total_page"].(int)
			case int64:
				totalPage = int(respBody["total_page"].(int64))
			case float64:
				totalPage = int(int64(respBody["total_page"].(float64)))
			default:
				totalPage = 1
			}
			totalCount := 0
			switch respBody["total_count"].(type) {
			case string:
				totalCount, _ = strconv.Atoi(respBody["total_count"].(string))
			case int:
				totalCount = respBody["total_count"].(int)
			case int64:
				totalCount = int(respBody["total_count"].(int64))
			case float64:
				totalCount = int(int64(respBody["total_count"].(float64)))
			default:
				totalCount = 1
			}
			if tc.Page > totalPage || tc.Size >= totalCount {
				logs.Info("Processed：tableName: ", tc.TableName, "Data acquisition completed， "+
					"total pages(page_num)：", totalPage, ", Total number(page_size)：", totalCount, "\n",
					",current page number：", tc.Page, ",Current number: ", tc.Size, ",url: ", compURL)
				break
			} else {
				logs.Info("current： tableName: ", tc.TableName, "Data acquisition completed， "+
					"total pages(page_num)：", totalPage, ", Total number(page_size)：", totalCount, "\n",
					",current page number：", tc.Page, ",Current number: ", tc.Size, ",url: ", compURL)
				lock.Lock()
				tc.Page = tc.Page + page
				tc.Size = tc.Size + curCount
				lock.Unlock()
				logs.Info("After the increase： tableName: ", tc.TableName, "Data acquisition completed， "+
					"total pages(page_num)：", totalPage, ", Total number(page_size)：", totalCount, "\n",
					",current page number：", tc.Page, ",Current number: ", tc.Size, ",url: ", compURL)
			}
			logs.Info("start: ", tc)
		} else {
			logs.Error("Network request failed，url:", compURL)
			continue
		}
	}
	*ch <- 1
	return "", nil
}

func ProcPackDetail(url string, value map[string]interface{}, tableValue models.GitPackageTable, i int) (string, error) {
	values := value
	var ge models.GitOpenEuler
	GitOpenEulerData(values, &ge, tableValue)
	// Query cpe data
	var pc models.PackageCpe
	pcError := models.GetCpePackName(&pc, ge.PackageName)
	if pcError == nil && pc.Id > 0 {
		ge.CpePackName = pc.CpePackName
	}
	logs.Info("Being processed packageName: ", ge.PackageName)
	ok, _ := models.GetSingleYaml(&ge)
	if ok {
		logs.Info("PackageName: ", ge.PackageName, ",Already exists, no need to insert again")
		gpinfo, okinfo := models.QueryCveOpeneulerDetaildataByName(ge.PackageName, ge.Version)
		if !okinfo && gpinfo.DetailId == 0 {
			_, pierr := GetPackageInfo(url, tableValue, ge)
			if pierr != nil {
				logs.Error("Failed to get package details, url: ", url,
					", PackageName: ", ge.PackageName, ",version: ", ge.Version, ",err: ", pierr)
				return ge.PackageName, pierr
			}
		}
		return ge.PackageName, nil
	}
	gitID, typeX, err := models.CreateYaml(&ge, tableValue)
	if gitID > 0 && err == nil {
		logs.Info("PackageName: ", ge.PackageName, typeX, "success, git_id: ", gitID)
	} else {
		logs.Error("PackageName: ", ge.PackageName, typeX, "failure, err: ", err)
		if strings.ContainsAny(string(err.Error()), "Duplicate entry") {
			ok, _ := models.GetSingleYaml(&ge)
			if ok {
				logs.Info("PackageName: ", ge.PackageName, ",Already exists, no need to insert again")
				gpinfo, okinfo := models.QueryCveOpeneulerDetaildataByName(ge.PackageName, ge.Version)
				if !okinfo && gpinfo.DetailId == 0 {
					_, pierr := GetPackageInfo(url, tableValue, ge)
					if pierr != nil {
						logs.Error("Failed to get package details, url: ", url, ", PackageName: ", ge.PackageName,
							",version: ", ge.Version, ",err: ", pierr)
						return ge.PackageName, pierr
					}
				}
				return ge.PackageName, nil
			}
		}
		return "", err
	}
	if typeX == "insert" && gitID > 0 {
		_, pierr := GetPackageInfo(url, tableValue, ge)
		if pierr != nil {
			logs.Error("Failed to get package details, url: ", url, ", PackageName: ", ge.PackageName, ",version: ", ge.Version, ",err: ", pierr)
			return ge.PackageName, pierr
		}
	}
	return "", nil
}

func GetPackageInfo(urls string, tableValue models.GitPackageTable, ge models.GitOpenEuler) (string, error) {
	scpURL := urls + "/packages/packageInfo" + "?table_name=" + tableValue.TableName + "&pkg_name=" + ge.PackageName
	logs.Info("PackageName: ", ge.PackageName, "Get packageInfo, request parameters: ", scpURL)
	var urlS url.URL
	q := urlS.Query()
	q.Add("table_name", tableValue.TableName)
	q.Add("pkg_name", ge.PackageName)
	params := q.Encode()
	req, _ := http.NewRequest("GET", urls, nil)
	req.URL = &url.URL{
		Scheme:   req.URL.Scheme,
		Host:     req.URL.Host,
		RawQuery: params,
		Path:     "/pkgmanage/packages/packageInfo",
	}

	req.URL.EscapedPath()
	body, err := util.HTTPGetCom(req.URL.String())
	var respBody map[string]interface{}
	if err == nil && body != nil {
		err = json.Unmarshal(body, &respBody)
		if err != nil {
			logs.Error(err)
			return "", err
		}
	} else {
		logs.Error("http request failed, scpURL: ", scpURL)
		return "", err
	}
	logs.Info(respBody)
	if respBody["code"].(string) == "2001" {
		if respBody["data"] == nil || respBody["data"] == "" {
			logs.Error("Failed to get package details, scpURL: ", scpURL)
			return "", errors.New("数据错误")
		}
		var gp models.GitPackageInfo
		GitOpenEulerInfoData(respBody["data"].(map[string]interface{}), &gp, ge)
		detailid, typex, err := models.CreateYamlDetail(&gp, ge)
		if detailid > 0 && typex == "insert" && err == nil {
			subdata := respBody["data"].(map[string]interface{})
			if subdata["subpack"] == nil {
				return "", err
			}
			if subdata == nil || subdata["subpack"] == nil || len(subdata["subpack"].([]interface{})) == 0 {
				return "", errors.New("数据错误")
			}
			subpack := subdata["subpack"].([]interface{})
			for _, packValuex := range subpack {
				logs.Info("yaml packValuex: ", packValuex)
				if packValuex == nil || packValuex == "" || len(packValuex.(map[string]interface{})) == 0 {
					continue
				}
				packValue := packValuex.(map[string]interface{})
				var gb models.GitSubPack
				gb.DetailId = gp.DetailId
				switch packValue["id"].(type) {
				case string:
					gb.Ids, _ = strconv.ParseInt(packValue["id"].(string), 10, 64)
				case int:
					gb.Ids = packValue["id"].(int64)
				case int64:
					gb.Ids = packValue["id"].(int64)
				case float64:
					gb.Ids = int64(packValue["id"].(float64))
				default:
					gb.Ids = 0
				}
				if packValue["name"] == nil {
					gb.SubPackName = ""
				} else {
					gb.SubPackName = packValue["name"].(string)
				}
				SubID, typex, err := models.CreateYamlSubPack(&gb)
				if SubID > 0 && typex == "insert" && err == nil {
					if packValue["provides"] != nil && len(packValue["provides"].([]interface{})) > 0 {
						provides := packValue["provides"].([]interface{})
						for _, provValuex := range provides {
							logs.Info("yaml provValuex: ", provValuex)
							if provValuex == nil || provValuex == "" || len(provValuex.(map[string]interface{})) == 0 {
								continue
							}
							provValue := provValuex.(map[string]interface{})
							var gs models.GitSubPackProvides
							gs.SubId = SubID
							switch provValue["id"].(type) {
							case string:
								gs.Ids, _ = strconv.ParseInt(provValue["id"].(string), 10, 64)
							case int:
								gs.Ids = provValue["id"].(int64)
							case int64:
								gs.Ids = provValue["id"].(int64)
							case float64:
								gs.Ids = int64(provValue["id"].(float64))
							default:
								gs.Ids = 0
							}
							if provValue["name"] == nil {
								gs.ProvideName = ""
							} else {
								gs.ProvideName = provValue["name"].(string)
							}
							gs.Requiredby = ""
							ProvideID, typexx, err := models.CreateYamlSubPackProvides(&gs)
							if ProvideID > 0 && typexx == "insert" && err == nil {
								if provValue["requiredby"] != nil && len(provValue["requiredby"].([]interface{})) > 0 {
									requiredby := provValue["requiredby"].([]interface{})
									for _, reqValue := range requiredby {
										if reqValue != nil && reqValue.(string) != "" {
											var gr models.GitSubPackRequiredby
											gr.ProvideId = gs.ProvideId
											gr.Requiredby = reqValue.(string)
											ID, typexy, err := models.CreateYamlSubPackRequiredb(&gr)
											logs.Info("CreateYamlSubPackRequiredb", ID, typexy, err)
										}
									}
								}
							}
						}
					}
					if packValue["requires"] != nil && len(packValue["requires"].([]interface{})) > 0 {
						requires := packValue["requires"].([]interface{})
						for _, reqValuexx := range requires {
							logs.Info("reqValuexx: ", reqValuexx)
							if reqValuexx == nil || reqValuexx == "" || len(reqValuexx.(map[string]interface{})) == 0 {
								continue
							}
							reqValuex := reqValuexx.(map[string]interface{})
							reqStr := ""
							if reqValuex["providedby"] != nil && len(reqValuex["providedby"].([]interface{})) > 0 {
								providedby := reqValuex["providedby"].([]interface{})
								for _, reqValue := range providedby {
									if reqValue != nil && reqValue.(string) != "" {
										reqStr = reqStr + reqValue.(string) + ","
									}
								}
							}
							if reqStr != "" {
								reqStr = reqStr[:len(reqStr)-1]
							}
							var gs models.GitSubPackRequire
							gs.SubId = SubID
							switch reqValuex["id"].(type) {
							case string:
								gs.Ids, _ = strconv.ParseInt(reqValuex["id"].(string), 10, 64)
							case int:
								gs.Ids = reqValuex["id"].(int64)
							case int64:
								gs.Ids = reqValuex["id"].(int64)
							case float64:
								gs.Ids = int64(reqValuex["id"].(float64))
							default:
								gs.Ids = 0
							}
							if reqValuex["name"] == nil {
								gs.RequireName = ""
							} else {
								gs.RequireName = reqValuex["name"].(string)
							}
							gs.Providedby = reqStr
							RequireID, typexx, err := models.CreateYamlSubPackRequires(&gs)
							logs.Info("CreateYamlSubPackRequires", RequireID, typexx, err)
						}
					}
				}
			}
		}
	}
	return "", nil
}

func GetEulerYamlInfo(url string) (string, error) {
	defer common.Catchs()
	compUrl := url + "?community=openeuler"
	body, err := util.HTTPGetCom(compUrl)
	var respBody map[string]interface{}
	if err == nil && body != nil {
		err = json.Unmarshal(body, &respBody)
		if err != nil {
			logs.Error(err)
			return "", err
		}
	} else {
		logs.Error("http Request failed， url: ", compUrl)
		return "", err
	}
	if respBody == nil || respBody["data"] == nil || len(respBody["data"].([]interface{})) == 0 {
		logs.Error("Data is empty, url: ", compUrl)
		return "", fmt.Errorf("Data is empty, url: %s", compUrl)
	}
	if respBody["code"].(float64) == 200 && respBody["total"].(float64) > 0 {
		//models.TruncateOpenEulerRepoOrigin()
		outSyncBranch := beego.AppConfig.String("cve::out_of_sync_branch")
		outSyncBranchList := strings.Split(outSyncBranch, ",")
		for _, values := range respBody["data"].([]interface{}) {
			valuesX := values.(map[string]interface{})
			ProcYamlInfo(valuesX, outSyncBranchList)
		}
	}
	return "", nil
}

// Parse the received json data
func ProcYamlInfo(valuesX map[string]interface{}, outSyncBranch []string) {
	if valuesX != nil && len(valuesX) > 0 {
		for repKey, repValue := range valuesX {
			if repKey != "" && len(repKey) > 1 {
				var branchs = make(map[string]string)
				//Delete all data first, then insert new data
				//models.DelOpenEulerRepoOriginByName(repKey)
				value := repValue.(map[string]interface{})
				brValue, brOk := value["branch_detail"]
				if brOk {
					brKey := brValue.([]interface{})
					if brKey != nil && len(brKey) > 0 {
						for _, branchValue := range brKey {
							var er models.OpenEulerRepoOrigin
							er.PackageName = repKey
							v := branchValue.(map[string]interface{})
							var ver string
							for k, vv := range v {
								if strings.EqualFold(k, "version") {
									ver = vv.(string)
									if _, ok := branchs[ver]; !ok {
										branchs[ver] = ""
									}
								}
							}
							for vKey, vu := range v {
								switch vKey {
								case "summary":
									er.Summary = vu.(string)
								case "version":
									er.Version = vu.(string)
								case "brname":
									er.Branchs = vu.(string)
									if !filterDataInSlice(er.Branchs, outSyncBranch) {
										branch, ok := branchs[ver]
										if !ok {
											branchs[ver] = vu.(string)
										} else {
											if len(branch) == 0 {
												branchs[ver] = vu.(string)
											} else {
												branchs[ver] = branch + "," + vu.(string)
											}
										}
									}
								case "description":
									switch vu.(type) {
									case []interface{}:
										des := vu.([]interface{})
										for _, d := range des {
											if er.Decription == "" {
												er.Decription = d.(string)
											} else {
												er.Decription = er.Decription + "," + d.(string)
											}
										}
									case string:
										er.Decription = vu.(string)
									}
								}
							}
							if len(er.Branchs) > 0 {
								outFlag := false
								for _, outBranch := range outSyncBranch {
									if outBranch == er.Branchs {
										outFlag = true
										break
									}
								}
								if outFlag {
									continue
								}
							}
							UpdateYamlToDb(er, branchs)
						}
					}
				}
			}
		}
	}
	return
}

// Update data to the database
func UpdateYamlToDb(er models.OpenEulerRepoOrigin, branchs map[string]string) {
	if len(er.PackageName) > 0 && len(er.Version) > 0 && !strings.Contains(er.Version, "%") {
		var oro models.OpenEulerRepoOrigin
		oro.PackageName = er.PackageName
		oro.Version = er.Version
		branch := branchs[er.Version]
		repErr := models.QueryEulerRepoOrigin(&oro, "PackageName", "Version")
		if oro.Id > 0 {
			if len(oro.Decription) < 2 && len(er.Decription) > 2 {
				oro.Decription = er.Decription
			}
			oro.Branchs = branch
			if len(oro.Summary) < 2 && len(er.Summary) > 2 {
				oro.Summary = er.Summary
			}
			oro.UpdateTime = common.GetCurTime()
			oro.Status = 1
			oro.Repo = er.PackageName
			upErr := models.UpdateEulerRepoOrigin(&oro, "Decription", "Branchs", "Summary", "UpdateTime", "Repo")
			logs.Info("UpdateEulerRepoOrigin, upErr: ", upErr)
		} else {
			logs.Info(repErr)
			oro.Decription = er.Decription
			oro.Branchs = branch
			oro.Summary = er.Summary
			oro.UpdateTime = common.GetCurTime()
			oro.PackageName = er.PackageName
			oro.Version = er.Version
			oro.Status = 1
			oro.Repo = er.PackageName
			oro.CreateTime = common.GetCurTime()
			id, inErr := models.InsertEulerRepoOrigin(&oro)
			logs.Info("InsertEulerRepoOrigin, id, inErr: ", id, inErr)
		}
	} else {
		logs.Error("UpdateYamlToDb, The version information of the current warehouse is wrong, er: ", er)
	}
}

// Synchronize other sources of yaml version
func SyncEulerYaml() {
	// Query raw data
	yamlId := int64(0)
	for {
		yamlList := models.GetEulerYamlList(yamlId, 100)
		if len(yamlList) > 0 {
			for _, yl := range yamlList {
				if len(yl.PackageName) < 2 || len(yl.Version) < 1 {
					logs.Error("yl: ", yl)
					continue
				}
				yamlId = yl.Id
				var ge models.GitOpenEuler
				ge.PackageName = yl.PackageName
				ge.Version = yl.Version
				geErr := models.QueryYamlData(&ge, "PackageName", "Version")
				if ge.GitId == 0 {
					logs.Info("yaml data does not exist, need to insert data, err: ",
						geErr, ",PackageName: ", yl.PackageName, ", Version: ", yl.Version)
					AddYamlData(&ge, yl)
				} else {
					EditYamlData(&ge, yl)
				}
			}
		} else {
			break
		}
	}
}

// Delete historical yaml source version data that does not exist
func DelHistoryEulerYaml() {
	gitId := int64(0)
	for {
		yamlList := models.GetGitOpenEulerList(gitId, 100)
		if len(yamlList) > 0 {
			for _, yl := range yamlList {
				if len(yl.PackageName) < 2 || len(yl.Version) < 1 {
					logs.Error("yl: ", yl)
					continue
				}
				gitId = yl.GitId
				var oro models.OpenEulerRepoOrigin
				oro.PackageName = yl.PackageName
				oro.Version = yl.Version
				repErr := models.QueryEulerRepoOrigin(&oro, "PackageName", "Version")
				if oro.Id == 0 {
					logs.Info("yaml data does not exist, delete data, err: ",
						repErr, ",PackageName: ", yl.PackageName, ", Version: ", yl.Version)
					yl.Status = 2
					upErr := models.UpdateYamlData(&yl, "Status")
					if upErr == nil {
						ge := models.GitPackageInfo{PackageName: yl.PackageName, Version: yl.Version}
						qErr := models.QueryGitPackageInfo(&ge, "PackageName", "Version")
						if qErr == nil {
							ge.Status = 2
							upgErr := models.UpdateGitPackageInfo(&ge, "Status")
							if upgErr != nil {
								logs.Error("UpdateGitPackageInfo, upgErr: ", upgErr)
							}
						}
					}
				}
			}
		} else {
			break
		}
	}
}

func AddYamlData(ge *models.GitOpenEuler, yl models.OpenEulerRepoOrigin) {
	ge.PackageName = yl.PackageName
	ge.Version = yl.Version
	ge.PackageId = 0
	ge.TableId = 0
	//ge.TableName = yl.Branchs
	ge.Status = 1
	ymalId, yErr := models.InsertYamlData(ge)
	if yErr == nil {
		ProcGitPackageInfo(ymalId, yl)
	}
}

func EditYamlData(ge *models.GitOpenEuler, yl models.OpenEulerRepoOrigin) {
	ge.Status = 1
	yErr := models.UpdateYamlData(ge, "Status")
	logs.Info("UpdateYamlData, yErr: ", yErr)
	ProcGitPackageInfo(ge.GitId, yl)
}

func ProcGitPackageInfo(ymalId int64, yl models.OpenEulerRepoOrigin) {
	gp := models.GitPackageInfo{GitId: ymalId, PackageName: yl.PackageName, Version: yl.Version}
	gpErr := models.QueryGitPackageInfo(&gp, "GitId", "PackageName", "Version")
	if gpErr != nil {
		gp.GitId = ymalId
		gp.PackageName = yl.PackageName
		gp.Version = yl.Version
		gp.Summary = yl.Summary
		gp.Decription = yl.Decription
		gp.Status = 0
		id, iErr := models.InsertGitPackageInfo(&gp)
		logs.Info("InsertGitPackageInfo, id, iErr", id, iErr)
	} else {
		gp.Summary = yl.Summary
		gp.Decription = yl.Decription
		gp.Status = 0
		uErr := models.UpdateGitPackageInfo(&gp, "Summary", "Decription", "Status")
		logs.Info("UpdateGitPackageInfo, uErr: ", uErr)
	}
}
